<?php
/**
 * Copyright (c) 2009, Jacek Karczmarczyk
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * - Neither the name of the author nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without
 *   specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * PHP Version 5
 *
 * @package Larchana_Controller
 * @version 0.1
 * @author Jacek Karczmarczyk <jacek@karczmarczyk.pl>
 */


/**
 * Access controller
 */
class Larchana_Controller_Action_Helper_Acl extends Zend_Controller_Action_Helper_Abstract
{
	/**
	 * @var array Login route
	 */
	protected $_loginRoute = null;

	/**
	 * @var Zend_Acl Acl object
	 */
	protected $_acl = null;

	/**
	 * @var string Current user role
	 */
	protected $_role ='default';

	/**
	 * Helper initialization, sets the _acl property from registry ('Zend_Acl')
	 */
	public function init()
	{
		if (Zend_Registry::isRegistered('Zend_Acl'))
			$this->_acl = Zend_Registry::get('Zend_Acl');
	}

	/**
	 * Sets permission for specified action and user role(s)
	 *
	 * @param string $action
	 * @return Larchana_Controller_Action_Helper_Acl Provides fluent interface
	 */
	public function allow($action)
	{
		if (!($acl = $this->getAcl())) return;

		// Create action resource
		//
		$resource = strtolower($this->getRequest()->getControllerName().':'.$action);
		if (!$acl->has($resource)) $acl->add(new Zend_Acl_Resource($resource));

		$roles = func_get_args();
		array_shift($roles);
		$acl->allow($roles, $resource);
		return $this;
	}

	/**
	 * Sets the login route
	 *
	 * @param string      $action Action name
	 * @param string|null $controller Controller name
	 * @param string|null $module Module name
	 * @param array|null  $params Params
	 * @return Larchana_Controller_Action_Helper_Acl Provides fluent interface
	 */
	public function setLoginRoute($action, $controller = null, $module = null, array $params = array())
	{
		if (!$controller) $controller = $this->getRequest()->getControllerName();
		if (!$module) $module = $this->getRequest()->getModuleName();
		if ($this->getRequest()->getActionName() != $action
		 || $this->getRequest()->getControllerName() != $controller
		 || $this->getRequest()->getModuleName() != $module)
		{
			$params['redirectTo'] = $_SERVER['REQUEST_URI'];
		}

		$this->_loginRoute = array(
			'action'=>$action,
			'controller'=>$controller,
			'module'=>$module,
			'params'=>$params
		);

		return $this;
	}

	/**
	 * Sets the _acl property
	 *
	 * @param Zend_Acl $acl Acl object
	 * @return Larchana_Controller_Action_Helper_Acl Provides fluent interface
	 */
	public function setAcl(Zend_Acl $acl)
	{
		$this->_acl = $acl;
		return $this;
	}

	/**
	 * Returns the _acl property or gets Acl object from registry
	 *
	 * @return Zend_Acl|null Acl object (if exists)
	 */
	public function getAcl()
	{
		return $this->_acl;
	}

	/**
	 * Sets current user role
	 *
	 * @param string $role User role
	 * @return Larchana_Controller_Action_Helper_Acl Provides fluent interface
	 */
	public function setRole($role)
	{
		$this->_role = $role ? $role : 'default';
		return $this;
	}

	/**
	 * Returns current user role
	 *
	 * @return string Current user role
	 */
	public function getRole()
	{
		return $this->_role;
	}

	/**
	 * Method called before dispatch
	 *
	 * Checks wether user is authorized to execute the current action
	 *
	 */
	public function preDispatch()
	{
		$controller = $this->getRequest()->getControllerName();
		$action = $this->getRequest()->getActionName();
		$resource = strtolower($controller.':'.$action);

		// Acl is not defined - all is allowed
		//
		if (!($acl = $this->getAcl()))
			return;

		// Check if action is allowed for user
		//
		if (!$acl->has($resource))
			$acl->add(new Zend_Acl_Resource($resource));
		if ($acl->isAllowed($this->getRole(), $resource))
			return;

		// User logged in but access is denied
		//
		if (Zend_Auth::getInstance()->hasIdentity())
			return $this->accessDenied();
	
		// Access is denied and user is not logged - forward to login page
		//
		$this->login();
	}

	/**
	 * Throws an exception
	 *
	 */
	public function accessDenied($message = null)
	{
		throw new Zend_Controller_Action_Exception($message ? $message : 'error.accessDenied');
	}
	
	/**
	 * Throws an exception 404 (not found)
	 * 
	 * @param null|string $message Error message
	 */
	public function notFound($message = null)
	{
		throw new Zend_Controller_Action_Exception($message, 404);
	}

	/**
	 * Forwards to login action
	 *
	 */
	public function login()
	{
		if ($this->getRequest()->isXmlHttpRequest())
			throw new Zend_Controller_Action_Exception("Your session has expired. Please refresh the page to login.");

		if (is_null($route = $this->_loginRoute))
			throw new Zend_Controller_Action_Exception("Login route not set");

		if (isset($route['params']))
			$this->getRequest()->setParams($route['params']);

		if (isset($route['controller']))
		{
			$this->getRequest()->setControllerName($route['controller']);

			if (isset($route['module']))
				$this->getRequest()->setModuleName($route['module']);
		}

		$this->getRequest()->setActionName($route['action'])->setDispatched(false);
	}
}
